Строки в си. Введение.
Это вводная статья по строкам в си. Более подробное описание и примеры будут, когда мы научимся работать с памятью и указателями. В компьютере все значения хранятся в виде чисел. И строки тоже, там нет никаких символов и букв. Строка представляет собой массив чисел. Каждое число соответствует определённому символу, который берётся из таблицы кодировки. При выводе на экран символ отображается определённым образом.
Для хранения строк используются массивы типа char
. Ещё раз повторюсь – тип char
– численный, он хранит один байт данных. Но в соответствии с таблицей кодировки каждое из этих чисел связано с символом. И в обратную сторону – каждый символ определяется своим порядковым номером в таблице кодировки.
Например
#include <conio.h>
#include <stdio.h>
void main() {
char c = 'A';
int i = 65;
printf("display as char %c\n", c);
printf("display as int %d\n", c);
printf("display as char %c\n", i);
printf("display as char %d\n", i);
_getch();
}
Мы создали две переменные, одна типа char
, другая int
. Литера 'A' имеет числовое значение 65. Это именно литера, а не строка, поэтому окружена одинарными кавычками. Мы можем вывести её на печать как букву
printf("display as char %c\n", c);
Тогда будет выведено
A
Если вывести её как число, то будет
65
Точно также можно поступить и с числом 65, которое хранится в переменной типа int
.
Спецсимволы также имеют свой номер
#include <conio.h>
#include <stdio.h>
void main() {
printf("%c", '\a');
printf("%d", '\a');
printf("%c", 7);
_getch();
}
Здесь будет сначала "выведен" звуковой сигнал, затем его числовое значение, затем опять звуковой сигнал.
Строка в си – это массив типа char
, последний элемент которого хранит терминальный символ '\0'. Числовое значение этого символа 0, поэтому можно говорить, что массив оканчивается нулём.
Например
#include <conio.h>
#include <stdio.h>
void main() {
char word[10];
word[0] = 'A';
word[1] = 'B';
word[2] = 'C';
word[3] = '\0';
//word[3] = 0; эквивалентно
printf("%s", word);
_getch();
}
Для вывода использовался ключ %s. При этом строка выводится до первого терминального символа, потому что функция printf не знает размер массива word.
Если в этом примере не поставить
word[3] = '\0';
то будет выведена строка символов произвольной длины, до тех пор, пока не встретится первый байт, заполненный нулями.
#include <conio.h>
#include <stdio.h>
void main() {
char word[10] = "ABC";
char text[100] = {'H', 'E', 'L', 'L', 'O'};
printf("%s\n", word);
printf("%s", text);
_getch();
}
В данном случае всё корректно. Строка "ABC" заканчивается нулём, и ею мы инициализируем массив word. Строка text инициализируется побуквенно, все оставшиеся символы, как следует из главы про массивы, заполняются нулями.
Чтение строк
Для того, чтобы запросить у пользователя строку, необходимо создать буфер. Размер буфера должен быть выбран заранее, так, чтобы введённое слово в нём поместилось. При считывании строк есть опасность того, что пользователь введёт данных больше, чем позволяет буфер. Эти данные будут считаны и помещены в память, и затрут собой чужие значения. Таким образом можно провести атаку, записав нужные байты, в которых, к примеру, стоит переход на участок кода с вредоносной программой, или логгирование данных.
#include <conio.h>
#include <stdio.h>
void main() {
char buffer[20];
scanf("%19s", buffer);
printf("%s", buffer);
_getch();
}
В данном случае количество введённых символов ограничено 19, а размер буфера на 1 больше, так как необходимо хранить терминальный символ. Напишем простую программу, которая запрашивает у пользователя строку и возвращает её длину.
#include <conio.h>
#include <stdio.h>
void main() {
char buffer[128];
unsigned len = 0;
scanf("%127s", buffer);
while (buffer[len] != '\0') {
len++;
}
printf("length(%s) == %d", buffer, len);
_getch();
}
Так как числовое значение символа '\0' равно нулю, то можно записать
while (buffer[len] != 0) {
len++;
}
Или, ещё короче
while (buffer[len]) {
len++;
}
Теперь напишем программу, которая запрашивает у пользователя два слова и сравнивает их
#include <conio.h>
#include <stdio.h>
/*
Результатом сравнения будет число
0 если слова равны
1 если первое слово больше второго в лексикографическом порядке
2 если второе слово больше
*/
void main() {
char firstWord[128]; //Первое слово
char secondWord[128]; //Второе слово
unsigned i; //Счётчик
int cmpResult = 0; //Результат сравнения
scanf("%127s", firstWord);
scanf("%127s", secondWord);
for (i = 0; i < 128; i++) {
if (firstWord[i] > secondWord[i]) {
//Больше даже если второе слово уже закончилось, потому что
//тогда оно заканчивается нулём
cmpResult = 1;
break;
} else if (firstWord[i] < secondWord[i]) {
cmpResult = -1;
break;
}
}
printf("%d", cmpResult);
_getch();
}
Так как каждая буква имеет числовое значение, то их можно сравнивать между собой как числа. Кроме того, обычно (но не всегда!) буквы в таблицах кодировок расположены по алфавиту. Поэтому сортировка по числовому значению также будет и сортировкой по алфавиту. Это не относится к различным реализациям стандарта Unicode: в них символы могут иметь переменную длину, сравнение часто осложнено наличием диакритических знаков и пр., поэтому для работы с отдельными символами используются специальные функции.
Существуют другие функции для чтения строк: часть из них небезопасна, другую часть мы изучим позднее, после изучения указателей и файловых потоков.